#pragma once

#include <cstdint>
#include <stdlib.h>
#include <vector>

class WriteBuf {
  uint8_t *buf_;
  const size_t bufsize_;
  size_t offset_ = 0;

public:
  WriteBuf(uint8_t *buf, size_t bufsize) : buf_(buf), bufsize_(bufsize) {}

  void reset() { offset_ = 0; }

  // Write a uint8_t to starting address &buf[pos].
  size_t write_uint8_at(size_t pos, uint8_t x);

  // Write a uint16_t to starting address &buf_[pos] (in big endian
  // order).
  size_t write_uint16_at(size_t pos, uint16_t x);

  // Write a uint32_t to starting address &buf_[pos] (in big endian
  // order).
  size_t write_uint32_at(size_t pos, uint32_t x);

  // Write a block of bytes to starting address &buf_[pos].
  size_t write_many_at(size_t pos, uint8_t x, size_t n);

  // Write a string to starting address &buf_[pos].
  size_t write_bytes_at(size_t pos, const uint8_t *bytes, size_t n);

  // Write a uint8_t to starting address &buf[offset_].
  void write_uint8(uint8_t x);

  // Write a uint16_t to starting address &buf_[offset_] (in big endian
  // order).
  void write_uint16(uint16_t x);

  // Write a uint32_t to starting address &buf_[offset_] (in big endian
  // order).
  void write_uint32(uint32_t x);

  // Write a block of bytes to starting address &buf_[offset_].
  void write_many(uint8_t x, size_t n);

  // Write n bytes to starting address &buf_[offset_].
  void write_bytes(const uint8_t *bytes, size_t n);

  // Write a string to starting address &buf_[offset_].
  void write_string(const char *str);

  size_t offset() const;

  // Inserts an n-byte gap, so that the bytes can be written later. This is
  // usually used for size or offset fields that need to be calculated
  // later.
  size_t insert_gap(size_t n);

  void write_to_fd(int fd);
};

// Utility for constructing a std::vector.
template <class... Ts>
std::vector<typename std::common_type<Ts...>::type> _vec(Ts &&...args) {
  std::vector<typename std::common_type<Ts...>::type> result;
  result.reserve(sizeof...(args));
  int bogus[] = {((void)result.emplace_back(std::forward<Ts>(args)), 0)...};
  static_assert(sizeof(bogus) == sizeof(int) * sizeof...(args));
  return result;
}

int compress(std::vector<uint8_t> &output, std::vector<uint8_t> &input);
